Skip to content

Method: static {...}

1: /*
2: * *********************************************************************************************************************
3: *
4: * TheseFoolishThings: Miscellaneous utilities
5: * http://tidalwave.it/projects/thesefoolishthings
6: *
7: * Copyright (C) 2009 - 2023 by Tidalwave s.a.s. (http://tidalwave.it)
8: *
9: * *********************************************************************************************************************
10: *
11: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
12: * the License. You may obtain a copy of the License at
13: *
14: * http://www.apache.org/licenses/LICENSE-2.0
15: *
16: * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
17: * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
18: * specific language governing permissions and limitations under the License.
19: *
20: * *********************************************************************************************************************
21: *
22: * git clone https://bitbucket.org/tidalwave/thesefoolishthings-src
23: * git clone https://github.com/tidalwave-it/thesefoolishthings-src
24: *
25: * *********************************************************************************************************************
26: */
27: package it.tidalwave.role.spi;
28:
29: import java.lang.reflect.Constructor;
30: import java.lang.reflect.InvocationTargetException;
31: import javax.annotation.Nonnull;
32: import javax.annotation.Nullable;
33: import java.util.ArrayList;
34: import java.util.Arrays;
35: import java.util.Collection;
36: import java.util.Comparator;
37: import java.util.HashSet;
38: import java.util.List;
39: import java.util.Map.Entry;
40: import java.util.Set;
41: import java.util.SortedSet;
42: import java.util.TreeSet;
43: import it.tidalwave.util.NotFoundException;
44: import it.tidalwave.util.annotation.VisibleForTesting;
45: import it.tidalwave.role.ContextManager;
46: import it.tidalwave.role.spi.impl.DatumAndRole;
47: import it.tidalwave.role.spi.impl.MultiMap;
48: import lombok.extern.slf4j.Slf4j;
49: import static it.tidalwave.role.spi.impl.LogUtil.*;
50:
51: /***********************************************************************************************************************
52: *
53: * A basic implementation of a {@link RoleManager}. This class must be specialized to:
54: *
55: * <ol>
56: * <li>discover roles (see {@link #scan(java.util.Collection)}</li>
57: * <li>associate roles to a datum (see {@link #findDatumTypesForRole(java.lang.Class)}</li>
58: * <li>associate roles to contexts (see {@link #findContextTypeForRole(java.lang.Class)}</li>
59: * <li>eventually retrieve beans to inject in created roles (see {@link #getBean(java.lang.Class)}</li>
60: * </ol>
61: *
62: * Specializations might use annotations or configuration files to accomplish these tasks.
63: *
64: * @author Fabrizio Giudici
65: *
66: **********************************************************************************************************************/
67: @Slf4j
68: public abstract class RoleManagerSupport implements RoleManager
69: {
70: @VisibleForTesting final MultiMap<DatumAndRole, Class<?>> roleMapByDatumAndRole = new MultiMap<>();
71:
72: // FIXME: use ConcurrentHashMap
73: @VisibleForTesting final Set<DatumAndRole> alreadyScanned = new HashSet<>();
74:
75: /*******************************************************************************************************************
76: *
77: * {@inheritDoc}
78: *
79: ******************************************************************************************************************/
80: @Override @Nonnull
81: public synchronized <ROLE_TYPE> List<? extends ROLE_TYPE> findRoles (@Nonnull final Object datum,
82: @Nonnull final Class<ROLE_TYPE> roleType)
83: {
84: log.trace("findRoles({}, {})", shortId(datum), shortName(roleType));
85: final Class<?> datumType = findTypeOf(datum);
86: final List<ROLE_TYPE> roles = new ArrayList<>();
87: final Set<Class<? extends ROLE_TYPE>> roleImplementationTypes = findRoleImplementationsFor(datumType, roleType);
88:
89: outer: for (final Class<? extends ROLE_TYPE> roleImplementationType : roleImplementationTypes)
90: {
91: for (final Constructor<?> constructor : roleImplementationType.getDeclaredConstructors())
92: {
93: log.trace(">>>> trying constructor {}", constructor);
94: final Class<?>[] parameterTypes = constructor.getParameterTypes();
95: Class<?> contextType = null;
96: Object context = null;
97:
98: try
99: {
100: contextType = findContextTypeForRole(roleImplementationType);
101: // With DI frameworks such as Spring it's better to avoid eager initializations of references
102: final ContextManager contextManager = ContextManager.Locator.find();
103: log.trace(">>>> contexts: {}", shortIds(contextManager.getContexts()));
104:
105: try
106: {
107: context = contextManager.findContextOfType(contextType);
108: }
109: catch (NotFoundException e)
110: {
111: log.trace(">>>> role {} discarded, can't find context: {}",
112: shortName(roleImplementationType), shortName(contextType));
113: continue outer;
114: }
115: }
116: catch (NotFoundException e)
117: {
118: // ok, no context
119: }
120:
121: try
122: {
123: final Object[] params = getParameterValues(parameterTypes, datumType, datum, contextType, context);
124: roles.add(roleType.cast(constructor.newInstance(params)));
125: break;
126: }
127: catch (InstantiationException | IllegalAccessException
128: | IllegalArgumentException | InvocationTargetException e)
129: {
130: log.error("Could not instantiate role of type " + roleImplementationType, e);
131: }
132: }
133: }
134:
135: if (log.isTraceEnabled())
136: {
137: log.trace(">>>> findRoles() returning: {}", shortIds(roles));
138: }
139:
140: return roles;
141: }
142:
143: /*******************************************************************************************************************
144: *
145: * Prepare the constructor parameters out of the given expected types. Parameters will be eventually made of the
146: * given datum, context, and other objects returned by {@link #getBean(java.lang.Class)}.
147: *
148: * @param parameterTypes the expected types
149: * @param datumClass the type of the datum
150: * @param datum the datum
151: * @param contextClass the type of the context
152: * @param context the context
153: *
154: ******************************************************************************************************************/
155: @Nonnull
156: private Object[] getParameterValues (@Nonnull final Class<?>[] parameterTypes,
157: @Nonnull final Class<?> datumClass,
158: @Nonnull final Object datum,
159: @Nullable final Class<?> contextClass,
160: @Nullable final Object context)
161: {
162: final List<Object> values = new ArrayList<>();
163:
164: for (final Class<?> parameterType : parameterTypes)
165: {
166: if (parameterType.isAssignableFrom(datumClass))
167: {
168: values.add(datum);
169: }
170: else if ((contextClass != null) && parameterType.isAssignableFrom(contextClass))
171: {
172: values.add(context);
173: }
174: else // generic injection
175: {
176: values.add(getBean(parameterType));
177: }
178: }
179:
180: log.trace(">>>> constructor parameters: {}", values);
181: return values.toArray();
182: }
183:
184: /*******************************************************************************************************************
185: *
186: * Finds the role implementations for the given owner type and role type. This method might discover new
187: * implementations that weren't found during the initial scan, since the initial scan can't go down in a
188: * hierarchy; that is, given a Base class or interface with some associated roles, it can't associate those roles
189: * to subclasses (or implementations) of Base. Now we can navigate up the hierarchy and complete the picture.
190: * Each new discovered role is added into the map, so the next time scanning will be faster.
191: *
192: * @param datumType the type of the datum
193: * @param roleType the type of the role to find
194: * @return the types of role implementations
195: *
196: ******************************************************************************************************************/
197: @Nonnull
198: @VisibleForTesting synchronized <RT> Set<Class<? extends RT>> findRoleImplementationsFor (
199: @Nonnull final Class<?> datumType,
200: @Nonnull final Class<RT> roleType)
201: {
202: final DatumAndRole datumAndRole = new DatumAndRole(datumType, roleType);
203:
204: if (!alreadyScanned.contains(datumAndRole))
205: {
206: alreadyScanned.add(datumAndRole);
207: final Set<Class<?>> before = new HashSet<>(roleMapByDatumAndRole.getValues(datumAndRole));
208:
209: for (final DatumAndRole superDatumAndRole : datumAndRole.getSuper())
210: {
211: roleMapByDatumAndRole.addAll(datumAndRole, roleMapByDatumAndRole.getValues(superDatumAndRole));
212: }
213:
214: final Set<Class<?>> after = new HashSet<>(roleMapByDatumAndRole.getValues(datumAndRole));
215: logChanges(datumAndRole, before, after);
216: }
217:
218: return (Set<Class<? extends RT>>)(Set)roleMapByDatumAndRole.getValues(datumAndRole);
219: }
220:
221: /*******************************************************************************************************************
222: *
223: * Scans all the given role implementation classes and build a map of roles by owner class.
224: *
225: * @param roleImplementationTypes the types of role implementations to scan
226: *
227: ******************************************************************************************************************/
228: protected synchronized void scan (@Nonnull final Collection<Class<?>> roleImplementationTypes)
229: {
230: log.debug("scan({})", shortNames(roleImplementationTypes));
231:
232: for (final Class<?> roleImplementationType : roleImplementationTypes)
233: {
234: for (final Class<?> datumType : findDatumTypesForRole(roleImplementationType))
235: {
236: for (final Class<?> roleType : findAllImplementedInterfacesOf(roleImplementationType))
237: {
238: if (!"org.springframework.beans.factory.aspectj.ConfigurableObject".equals(roleType.getName()))
239: {
240: roleMapByDatumAndRole.add(new DatumAndRole(datumType, roleType), roleImplementationType);
241: }
242: }
243: }
244: }
245:
246: logRoles();
247: }
248:
249: /*******************************************************************************************************************
250: *
251: * Finds all the interfaces implemented by a given class, including those eventually implemented by superclasses
252: * and interfaces that are indirectly implemented (e.g. C implements I1, I1 extends I2).
253: *
254: * @param clazz the class to inspect
255: * @return the implemented interfaces
256: *
257: ******************************************************************************************************************/
258: @Nonnull
259: @VisibleForTesting static SortedSet<Class<?>> findAllImplementedInterfacesOf (@Nonnull final Class<?> clazz)
260: {
261: final SortedSet<Class<?>> interfaces = new TreeSet<>(Comparator.comparing(Class::getName));
262: interfaces.addAll(Arrays.asList(clazz.getInterfaces()));
263:
264: for (final Class<?> interface_ : interfaces)
265: {
266: interfaces.addAll(findAllImplementedInterfacesOf(interface_));
267: }
268:
269: if (clazz.getSuperclass() != null)
270: {
271: interfaces.addAll(findAllImplementedInterfacesOf(clazz.getSuperclass()));
272: }
273:
274: return interfaces;
275: }
276:
277: /*******************************************************************************************************************
278: *
279: * Retrieves an extra bean.
280: *
281: * @param <T> the static type of the bean
282: * @param beanType the dynamic type of the bean
283: * @return the bean
284: *
285: ******************************************************************************************************************/
286: @Nullable
287: protected abstract <T> T getBean (@Nonnull Class<T> beanType);
288:
289: /*******************************************************************************************************************
290: *
291: * Returns the type of the context associated to the given role implementation type.
292: *
293: * @param roleImplementationType the role type
294: * @return the context type
295: * @throws NotFoundException if no context is found
296: *
297: ******************************************************************************************************************/
298: @Nonnull
299: protected abstract Class<?> findContextTypeForRole (@Nonnull Class<?> roleImplementationType)
300: throws NotFoundException;
301:
302: /*******************************************************************************************************************
303: *
304: * Returns the valid datum types for the given role implementation type.
305: *
306: * @param roleImplementationType the role type
307: * @return the datum types
308: *
309: ******************************************************************************************************************/
310: @Nonnull
311: protected abstract Class<?>[] findDatumTypesForRole (@Nonnull Class<?> roleImplementationType);
312:
313: /*******************************************************************************************************************
314: *
315: *
316: ******************************************************************************************************************/
317: private void logChanges (@Nonnull final DatumAndRole datumAndRole,
318: @Nonnull final Set<Class<?>> before,
319: @Nonnull final Set<Class<?>> after)
320: {
321: after.removeAll(before);
322:
323: if (!after.isEmpty())
324: {
325: log.debug(">>>>>>> added implementations: {} -> {}", datumAndRole, shortNames(after));
326:
327: if (log.isTraceEnabled()) // yes, trace
328: {
329: logRoles();
330: }
331: }
332: }
333:
334: /*******************************************************************************************************************
335: *
336: *
337: ******************************************************************************************************************/
338: public void logRoles()
339: {
340: log.debug("Configured roles:");
341:
342: final List<Entry<DatumAndRole, Set<Class<?>>>> entries = new ArrayList<>(roleMapByDatumAndRole.entrySet());
343: entries.sort(Comparator.comparing((Entry<DatumAndRole, Set<Class<?>>> e) -> e.getKey()
344: .getDatumClass()
345: .getName())
346: .thenComparing(e -> e.getKey().getRoleClass().getName()));
347:
348: for (final Entry<DatumAndRole, Set<Class<?>>> entry : entries)
349: {
350: log.debug(">>>> {}: {} -> {}",
351: shortName(entry.getKey().getDatumClass()),
352: shortName(entry.getKey().getRoleClass()),
353: shortNames(entry.getValue()));
354: }
355: }
356:
357: /*******************************************************************************************************************
358: *
359: * Returns the type of an object, taking care of mocks created by Mockito, for which the implemented interface is
360: * returned.
361: *
362: * @param object the object
363: * @return the object type
364: *
365: ******************************************************************************************************************/
366: @Nonnull
367: @VisibleForTesting static <T> Class<T> findTypeOf (@Nonnull final T object)
368: {
369: Class<?> ownerClass = object.getClass();
370:
371: if (ownerClass.toString().contains("MockitoMock"))
372: {
373: ownerClass = ownerClass.getInterfaces()[0]; // 1st is the original class, 2nd is CGLIB proxy
374:
375: if (log.isTraceEnabled())
376: {
377: log.trace(">>>> owner is a mock {} implementing {}",
378: shortName(ownerClass), shortNames(Arrays.asList(ownerClass.getInterfaces())));
379: log.trace(">>>> owner class replaced with {}", shortName(ownerClass));
380: }
381: }
382:
383: return (Class<T>)ownerClass;
384: }
385: }